﻿#region using
using System;
using System.Collections.Generic;
using System.IO;
#endregion using

namespace MpSoft.IO
{
	public static class StreamExt
	{
		public static bool Find(this Stream stream,byte[] toFind)
		{
			ulong position;
			return Find(stream,toFind,out position);
		}

		public static bool Find(this Stream stream,byte[] toFind,out ulong position)
		{
			if (stream==null)
				throw new ArgumentNullException("stream");
			if (toFind==null)
				throw new ArgumentNullException("toFind");
			if (toFind.Length==0)
				throw new ArgumentOutOfRangeException("toFind");

			List<PartialFind> partialFinds=new List<PartialFind>();
			byte firstToFind=toFind[0];
			int toFindLen=toFind.Length-1;
			bool toFindLenIsOne=toFindLen==0;

			ulong pos=0;
			int readVal;
			byte curr;
			while ((readVal=stream.ReadByte())!=-1)
			{
				curr=(byte)readVal;
				pos++;

				if (curr==firstToFind)
				{
					if (toFindLenIsOne)
					{
						position=pos-1;
						return true;
					}
					partialFinds.Add(new PartialFind() { Pos=pos });
				}

				int pfCount=partialFinds.Count;
				for (int a=pfCount-1;a>=0;a--)
				{
					PartialFind pf=partialFinds[a];
					if (curr==toFind[pf.Bytes.Count])
					{
						if (pf.Bytes.Count==toFindLen)
						{
							position=pf.Pos-1;
							return true;
						}
						else
							pf.Bytes.Add(curr);
					}
					else
						partialFinds.RemoveAt(a);
				}
			}
			position=default(ulong);
			return false;
		}

		public static IEnumerable<ulong> FindAll(this Stream stream,byte[] toFind)
		{
			List<ulong> result=new List<ulong>();
			ulong pos,lastPos=0;
			int toFindLen=toFind.Length;
			while (stream.Find(toFind,out pos))
			{
				pos+=lastPos;
				result.Add(pos);
				lastPos=pos+(ulong)toFindLen;
			}
			return result;
		}

		class PartialFind
		{
			internal ulong Pos;
			internal List<byte> Bytes=new List<byte>();
		}
	}
}
